﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Windows.Forms;
using SQLLink.DataLayer;
using SQLLink.DomainClasses;

namespace Localizer
{
    public partial class Localizer : Form
    {
        private FormWindowState previousWindowState = FormWindowState.Normal; //The previous state of the window
        private Context Context      = new Context();                         //The context (accesses the database)
        private BusLine LocalBusLine { get; set; }                            //The user selected busLine

        /// <summary>
        /// Localizer()
        /// Creates a new instance of the localizer form
        /// </summary>
        public Localizer()
        {
            // Pops the form
            InitializeComponent();
            
            // List the available buslines
            ListBuslines();
        }

        /// <summary>
        /// ListBusLines()
        /// List all the busLines from the demoBusLines folder
        /// </summary>
        private void ListBuslines()
        {
            var cd = Directory.GetCurrentDirectory() + "\\..\\..\\Buslines\\";
 
            /////////////////////////////////////////////////////////////////////////////////
            // This whole commented section was used to convert GTFS Files into busline files
            // We can uncomment it in order to modify the way that the files are created
            ///////////////////////////////////////////////////////////////////////////////// 
            //var GTFSFolderPath = cd + "GTFS Data\\";
            //var allGTFSFiles = Directory.GetFiles(GTFSFolderPath);

            //var stopsFile = allGTFSFiles.ToList().Find(x => x.Contains("stops.txt"));
            //var stopTimesFile = allGTFSFiles.ToList().Find(x => x.Contains("stop_times.txt"));

            //var stopsFileLines = File.ReadAllLines(stopsFile, Encoding.Default).ToList();
            //var stopTimesFileLine = File.ReadAllLines(stopTimesFile, Encoding.Default).ToList();
            //stopsFileLines.RemoveAt(0);
            //stopTimesFileLine.RemoveAt(0);

            //var markerList = new List<Marker>();
            //var GTFSBusLinesList = new List<BusLine>();

            //foreach (var line in stopsFileLines)
            //{
            //    var split = line.Split(',');
            //    var lat = double.Parse(split[3].Replace('.', ','));
            //    var lon = double.Parse(split[4].Replace('.', ','));
            //    var latlon = new LatLon(lat, lon);
            //    var name = split[1];
            //    var id = int.Parse(split[0]);
            //    var marker = new Marker(latlon, name, id, MarkerType.other);
            //    markerList.Add(marker);
            //}

            //var lastBusLineId = 0;
            //foreach (var line in stopTimesFileLine)
            //{
            //    var split = line.Split(',');
            //    var longID = split[0].Split('_');
            //    var id = int.Parse(longID[0]);
            //    var stop = int.Parse(split[3]);
            //    var marker = markerList.Find(x => x.Id == stop);

            //    if (lastBusLineId != id)
            //    {
            //        GTFSBusLinesList.Add(new BusLine{Id = id});
            //    }
            //    else if (!GTFSBusLinesList.Last().Markers.Contains(marker))
            //    {
            //        GTFSBusLinesList.Last().Markers.Add(marker);
            //    }

            //    lastBusLineId = id;
            //}

            //foreach (var busline in GTFSBusLinesList)
            //{
            //    var stream = File.Create(cd + "Demo Buslines\\" + busline.Id + ".busline");
            //    foreach (var marker in busline.Markers)
            //    {
            //        // The markers are doubled (to and fro busrides)
            //        if (busline.Markers.Count/2 < busline.Markers.IndexOf(marker))
            //        {   
            //            if(busline.Markers.IndexOf(marker) % 2 == 0 ||                     //We take only half of the markers
            //               busline.Markers.First() == marker ||                            //We absolutely take the first one
            //               busline.Markers.ElementAt(busline.Markers.Count / 2) == marker) //We absolutely take the last one
            //            {
            //                var buffer = Encoding.Default.GetBytes(marker.Name + ", Latlon : " + marker.LatLon + "\n");
            //                stream.Write(buffer, 0, buffer.Length);
            //            }
            //        }
            //    }
            //    stream.Close();
            //}
            /////////////////////////////////////////////////////////////////////////////////
           
            //Creates the path to the demo busLines
            var demoFolderPath = cd + "Demo Buslines\\";
            var substringIndex = demoFolderPath.Count();
            
            //Gets all the demo files
            var allDemoFiles = Directory.GetFiles(demoFolderPath, "*.busline").ToList();
            
            //Creates a new list of busLine
            var items = new List<BusLine>();

            foreach (var file in allDemoFiles)
            {
                //Parses the busLine number
                int number;
                int.TryParse(file.Substring(substringIndex).Replace(".busline", ""), out number);
                
                //Sets the localizerPath to the busLine
                var localizerPath = file;

                //Creates the new busLine
                var busLine = new BusLine
                                  {
                                      Number = number,
                                      LocalizerPath = localizerPath
                                  };

                // Adds the busLine to the list of items
                items.Add(busLine);
            }

            //Sorts the busLines with their numbers
            items.Sort((x,y) => x.Number.CompareTo(y.Number));
            
            //Adds the busLines to the combo box
            AvailableBuslinesComboBox.Items.AddRange(items.ToArray());

            //Hides or display controls
            if(AvailableBuslinesComboBox.Items.Count > 0)
            {
                OwnerLabel.Visible = true;
                OwnerComboBox.Visible = true;
                AvailableBuslinesComboBox.SelectedIndex = 0;
            }
            else
            {
                OwnerLabel.Visible = false;
                OwnerComboBox.Visible = false;
            }
        }

        /// <summary>
        /// AvailableBuslinesComboBox_SelectedIndexChanged(object, EventArgs)
        /// This method is called whenever the selected index changes in the available busline combo box
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">the arguments of the event</param>
        private void AvailableBuslinesComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            //Gets the busLine Path
            var busLinePath = ((BusLine) AvailableBuslinesComboBox.SelectedItem).LocalizerPath;
            
            //Gets the lines of the file
            var lines = File.ReadAllLines(busLinePath, Encoding.Default);
            
            //Creates a list of marker
            var allMarkers = new List<Marker>();
            
            //Property used to rewrite the file when it is corrupt
            var willRewrite = false;

            //Clears the item from the combo box
            OwnerComboBox.Items.Clear();
            
            //Iterates over the lines
            for (var index = 0; index < lines.Length; index++)
            {
                //This string must be found in a line in order to consider the whole file ok
                const string strLatLon = ", Latlon : ";
                
                //Get the line
                var line = lines[index];
                
                //Creates a latlon object
                LatLon latlon;

                //Creates a string object representing the name of the marker
                string markerName;
                
                //In the case the *.busline file already contains the latitude and longitude
                if (line.Contains(strLatLon) && !line.Contains("0, 0"))
                {
                    //Positions to the strLatLon
                    var strLatLonIndex = line.IndexOf(strLatLon);
                    
                    //Positions after the strLatLon
                    var latlonIndex = strLatLonIndex + strLatLon.Length;
                    
                    //Gets the length of the latlon
                    var length = line.Length - latlonIndex;

                    //Gets the string representing the latlon
                    var latlonStr = line.Substring(latlonIndex, length);
                    
                    //Splits the latlonstr in order to generate the lat and the lon separately
                    var split = latlonStr.Split(',');

                    //Affects the lat and lon
                    var lat = double.Parse(split[0].Replace('.', ','));
                    var lon = double.Parse(split[1].Replace('.', ','));

                    //Sets the latlon object created earlier
                    latlon = new LatLon(lat, lon);

                    //Gets the name of the marker created earlier
                    markerName = line.Substring(0, strLatLonIndex);
                }
                //Else, we need to ask google for the latitude and longitude and add the latlon to the file
                else
                {
                    //The file was corrupt
                    willRewrite = true;

                    //Queries google API
                    latlon = new LatLon(line);
                    
                    //The markername is the whole file line
                    markerName = line;

                    //Adds the missing latlon to the lines of the allLines string array
                    lines[index] += strLatLon + latlon;
                }
                
                //Creates a marker for the line
                var marker = new Marker(latlon, markerName, index, MarkerType.none);
                
                //Adds the marker as a selectable owner
                OwnerComboBox.Items.Add(marker);

                //Adds the marker to the local list of markers
                allMarkers.Add(marker);
            }

            //If the file was corrupt
            if(willRewrite)
            {
                var bufferStr = "";
                //Iterates on the lines
                foreach (var line in lines)
                {
                    bufferStr += line + "\r\n";
                }

                //Creates a buffer in byte[]
                var buffer = Encoding.Default.GetBytes(bufferStr);
                
                //Recreates the corrupted file
                var stream = File.Create(busLinePath);
                
                //Writes to the file
                stream.Write(buffer, 0, buffer.Length);
                
                //Closes the file
                stream.Close();
            }

            //Gets the number of the selecte busLine
            var number = ((BusLine)AvailableBuslinesComboBox.SelectedItem).Number;
            
            //Sets the local busline to the selected busline
            LocalBusLine = new BusLine { Busses = new List<Bus>(), GPRMCs = new List<GPRMC>(), Markers = allMarkers, Number = number};
            
            //Sets the type of the markers of the busline according to their order
            LocalBusLine.SetTypeAccordingToOrder();

            //Selects a default owner marker
            if(AvailableBuslinesComboBox.Items.Count > 0)
                OwnerComboBox.SelectedIndex = 0;
        }

        /// <summary>
        /// OwnerComboBox_SelectedIndexChanged(object, EventArgs)
        /// This method is called whenever the selected index of the owner combo box changes
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The arguments of the event</param>
        private void OwnerComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            //Retrieves the Id of the selected owner marker
            var markerId = OwnerComboBox.SelectedIndex;
            
            //Finds all the busses in the marker list
            var busses = LocalBusLine.Markers.FindAll(x => x.Type == MarkerType.bus);
            
            //Removes all the busses from the marker list
            LocalBusLine.Markers.RemoveAll(x => x.Type == MarkerType.bus);
            
            //Sets the selected marker to owner
            LocalBusLine.SetOwner(LocalBusLine.Markers[markerId]);
            
            //Adds back the busses
            LocalBusLine.Markers.AddRange(busses);
            
            //Sorts the markers with their proper Ids
            LocalBusLine.SortMarkers();
        }

        /// <summary>
        /// RefreshButton_Click(object, EventArgs)
        /// This method is called whenever a user clicks the refresh button
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The event arguments</param>
        private void RefreshButton_Click(object sender, EventArgs e)
        {
            //Calls the refreshAll method
            RefreshAll();
        }

        /// <summary>
        /// RefreshAll()
        /// This method is called when a need to refresh the map has been expressed
        /// </summary>
        private void RefreshAll()
        {
            //Tells the user the map is refreshing
            labelMapStatus.Text = @"Rafraîchissement en cours";
            labelMapStatus.Location = new Point(MapBox.Location.X + MapBox.Width / 2 - labelMapStatus.Width / 2,
                                                MapBox.Location.Y + MapBox.Height / 2 - labelMapStatus.Height / 2);
            labelMapStatus.Visible = true;
            
            //Refreshes the form
            Refresh();

            //Gets the map
            GetGPSMap();

            //Refreshes the distance and duration for the next bus
            RefreshDistanceAndDuration();

            //Kills the message
            labelMapStatus.Visible = false;
        }
        
        /// <summary>
        /// GetGPSMap()
        /// This methods is used to refresh the googlePictureBox
        /// </summary>
        private void GetGPSMap()
        {
            //Removes all the busses from the localBusLine (because they changed location)
            LocalBusLine.Busses.RemoveAll(x => true);

            //Gets the busLine data from the database
            var BusLine = Context.GetBusLine(LocalBusLine.Number);

            //Adds the busses with their new location
            if (BusLine != null && BusLine.Busses != null)
            {
                LocalBusLine.Busses.AddRange(BusLine.Busses);
            }

            //Refreshes the bus
            LocalBusLine.RefreshBusses();

            try
            {
                //Gets the map
                MapBox.LoadAsync(MapBox.GetMap(LocalBusLine.Markers.FindAll(x => !x.HasPassed && x.Localized))); // Loads the image
            }
            catch (WebException)
            {
                //If getting the map resulted in an error
                var dr = MessageBox.Show(@"Une connexion Internet est nécessaire afin de réaliser cette action!",
                                         @"Connexion Internet absente",
                                         MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
                if (dr == DialogResult.Retry)
                {
                    //User can retry
                    GetGPSMap();
                }
            }
            
        }

        /// <summary>
        /// RefreshDistanceAndDuration()
        /// This method is used to refresh the distance and duration string visible in the right corner of the map
        /// </summary>
        private void RefreshDistanceAndDuration()
        {
            if (LocalBusLine.GetNumberOfComingBusses() > 0)
            {
                // Creates a googleDistanceMatrix container
                var gdm = new GDM();

                //Creates the origin and destinations strings
                var origin = LocalBusLine.GetOwnerLatLon().ToString();
                var destinations = LocalBusLine.ComingBussesToString();

                // Creates and gets the google distance matrix from google API
                var distanceDurationMatrix = gdm.GetMatrix(origin, destinations);
                var busDistances = distanceDurationMatrix.GetDistanceValues();
                var busTimes = distanceDurationMatrix.GetDurationValues();

                if (busDistances.Count > 0 && busTimes.Count > 0)
                {
                    //Sorts the distances and the durations
                    busDistances.Sort();
                    busTimes.Sort();

                    //Gets the next bus distance
                    var nextBusDistance = busDistances.First();
                    
                    //Gets the next bus time
                    var nextBusTime = busTimes.First();

                    // modifies the string of the nextBusLabel
                    NextBusLabel.Text = @"Le prochain autobus est à " + GetCoherentDistanceString(nextBusDistance) + @". " +
                                        @"Il devrait arriver dans " + GetCoherentTimeString(nextBusTime) + @".";
                }
            }
            else if (LocalBusLine.Busses.Count > 0)
            {
                NextBusLabel.Text = @"Les autobus de cette ligne ont dépassé votre arrêt!";
            }
            else
            {
                // Tells the user there are no busses yet
                NextBusLabel.Text = @"Aucun autobus en route pour l'instant";
            }
        }
        
        /// <summary>
        /// GetCoherentTimeString(int)
        /// This method is used to get a coherent time string (mm:ss)
        /// </summary>
        /// <param name="timeInSeconds">The time to express coherently</param>
        /// <returns></returns>
        private static string GetCoherentTimeString(int timeInSeconds)
        {
            const int SECONDS_PER_MINUTES = 60;
            var minutes = timeInSeconds / SECONDS_PER_MINUTES;

            if(minutes > 0)
                return minutes + "m" + timeInSeconds%SECONDS_PER_MINUTES + "s";

            return timeInSeconds + "s";
        }

        /// <summary>
        /// GetCoherentDistanceString(int)
        /// This method is used to get a coherent time string km,m)
        /// </summary>
        /// <param name="distanceInMeters">The distance to express coherently</param>
        /// <returns></returns>
        private static string GetCoherentDistanceString(int distanceInMeters)
        {
            const int METERS_PER_KILOMETERS = 1000;
            var kilometers = distanceInMeters / METERS_PER_KILOMETERS;

            if (kilometers > 0)
                return kilometers + "." + Math.Round((decimal) distanceInMeters%METERS_PER_KILOMETERS) + "km";
           
            return distanceInMeters + "m";
        }


        /// <summary>
        /// RefreshMapDelay_Tick(object, EventArgs)
        /// This method is used to prevent the user from clicking to fast on the refresh button
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The event arguments</param>
        private void RefreshMapDelay_Tick(object sender, EventArgs e)
        {
            //It enables the delay when it's disabled
            if (!RefreshMapDelay.Enabled)
            {
                RefreshMapDelay.Enabled = true;
                RefreshButton.Enabled = false;
            }
            //It disables the delay when it's enabled
            else
            {
                RefreshMapDelay.Enabled = false;
                RefreshButton.Enabled = true;
            }
        }

        /// <summary>
        /// NextBusLabel_SizeChange(object, EventArgs)
        /// This method is called when the size of the control "nextBusLabel" changes
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The event arguments</param>
        private void NextBusLabel_SizeChanged(object sender, EventArgs e)
        {
            //Replaces the label correctly in the map
            ((Label)sender).Location = new Point(Width - (((Label)sender).Width + 24), ((Label)sender).Location.Y);
        }

        /// <summary>
        /// Localizer_ResizeEnd(object, EventArgs)
        /// This method is called whenever a user ends resizing the form
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The event arguments</param>
        private void Localizer_ResizeEnd(object sender, EventArgs e)
        {
            //Calls the refreshAll method
            RefreshAll();
        }


        /// <summary>
        /// Localizer_Resize(object, EventArgs)
        /// This method is called whenever a user resizes the form
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The event arguments</param>
        private void Localizer_Resize(object sender, EventArgs e)
        {
            //Maximised the form
            if (WindowState == FormWindowState.Maximized && previousWindowState == FormWindowState.Normal)
            {
                previousWindowState = FormWindowState.Maximized;
                FormBorderStyle = FormBorderStyle.None;
                RefreshAll();
            }

            //Return the form to its normal size
            else if (WindowState != FormWindowState.Maximized && previousWindowState == FormWindowState.Maximized)
            {
                previousWindowState = FormWindowState.Normal;
                FormBorderStyle = FormBorderStyle.Sizable;
                RefreshAll();
            }
        }

        /// <summary>
        /// Localizer_KeyDown(object, KeyEventArgs)
        /// This method happens whenever the user pressed a key on the keyboard
        /// </summary>
        /// <param name="sender">The event sender</param>
        /// <param name="e">The event arguments</param>
        private void Localizer_KeyDown(object sender, KeyEventArgs e)
        {
            //Only if the escape key is pressed
            if (e.KeyCode == Keys.Escape)
            {
                //Returns to the normal window state
                WindowState = FormWindowState.Normal;

                //Sets the form bounds
                Bounds = RestoreBounds;
            }
        }
    }
}
